package com.spel.validate;

import org.apache.commons.lang3.BooleanUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.NoUniqueBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.expression.BeanFactoryResolver;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.expression.Expression;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.TypedValue;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Map;

/**
 * @title: PermissionExpressionParser
 * @Author xu-xiang
 * @Date: 2024/1/30 11:41
 * @description: 权限表达式解析器
 */
@Component
public class PermissionExpressionParser implements ApplicationContextAware {

    private static final Logger logger = LoggerFactory.getLogger(PermissionExpressionParser.class);

    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();

    private ApplicationContext applicationContext;

    public Object parseExpression(Method method,
                                  Object[] args,
                                  String expressionString,
                                  Class<?> beanClass,
                                  AssertExceptionCondition ifErrorCondition
    ) {

        // 解析类,解析一个spring bean
        if (beanClass == Void.class) {
            return this.parseExpression(method, args, expressionString, TypedValue.NULL, ifErrorCondition);
        } else {
            Object springBean = this.getBean(beanClass);
            return this.parseExpression(method, args, expressionString, springBean, ifErrorCondition);
        }
    }

    public Object parseExpression(Method method, Object[] args, String expressionString, Object rootObject,
                                  AssertExceptionCondition ifErrorCondition) {
        Expression expression = EXPRESSION_PARSER.parseExpression(expressionString);
        StandardEvaluationContext context = this.getEvaluationContext(method, args, rootObject);
        //获取解析计算的结果
        Object expressionValue = expression.getValue(context);
        if (expressionValue instanceof Boolean) {
            if (BooleanUtils.isFalse((Boolean) expressionValue)) {
                Class<? extends RuntimeException> errorClazz = ifErrorCondition.getIfError();
                try {
                    Constructor<? extends RuntimeException> exceptionConstructor = errorClazz.getDeclaredConstructor(String.class);
                    // 断言失败, 抛出自定义异常
                    logger.error("进行{}时,验证未通过!", ifErrorCondition.getAssertType().getDesc());
                    throw exceptionConstructor.newInstance(ifErrorCondition.getErrorMsg());
                } catch (Exception e) {
                    logger.error("new Instance error, object: " + errorClazz.getName(), e);
                    throw new RuntimeException(e);
                }
            }
        }
        return expressionValue;
    }

    private StandardEvaluationContext getEvaluationContext(Method method, Object[] args, Object rootObject) {
        // 初始化Sp el表达式上下文，并设置 AuthService
        StandardEvaluationContext context = new StandardEvaluationContext(rootObject);

        // 设置表达式支持spring bean
        context.setBeanResolver(new BeanFactoryResolver(applicationContext));
        for (int i = 0; i < args.length; i++) {
            // 读取方法参数
            MethodParameter methodParam = this.getMethodParameter(method, i);
            // 设置方法 参数名和值 为spEL变量
            context.setVariable(methodParam.getParameterName(), args[i]);
        }
        return context;
    }

    private MethodParameter getMethodParameter(Method method, int parameterIndex) {
        ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();
        MethodParameter methodParameter = new SynthesizingMethodParameter(method, parameterIndex);
        methodParameter.initParameterNameDiscovery(PARAMETER_NAME_DISCOVERER);
        return methodParameter;
    }

    private Object getBean(Class<?> clazz) {
        Assert.isTrue(!clazz.isEnum(), "required a class of spring bean");
        Map<String, ?> beansOfType = applicationContext.getBeansOfType(clazz);

        if (CollectionUtils.isEmpty(beansOfType)) {
            throw new NoSuchBeanDefinitionException(clazz);
        }

        // spring ioc容器只有只一个bean,直接返回即可
        if (beansOfType.size() == 1) {
            return beansOfType.values().toArray()[0];
        }

        // 至此, spring ioc容器有多个此类型的实例对象(也可能是唯一bean但是不同名)
        // class是接口或者抽象类, 那不可能匹配到具体的bean
        if (clazz.isInterface() || Modifier.isAbstract(clazz.getModifiers())) {
            throw new NoUniqueBeanDefinitionException(clazz);
        }

        for (Map.Entry<String, ?> beanEntry : beansOfType.entrySet()) {
            if (beanEntry.getValue().getClass().equals(clazz)) {
                return beanEntry.getValue();
            }

            logger.debug("发现{}其他的子类实例, bean名称:{}", clazz.getName(), beanEntry.getKey());
        }

        return null;
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}
